/*
 * Copyright (c)  2020,  DataPipeline
 * All rights reserved.
 *
 * Id:EnumUtils.java   2020-07-18 22:22 wanglijun
 */
package cn.datacare.orm.util;

import cn.datacare.orm.annotation.EnumMapping;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * Title: EnumUtils
 * </p>
 * <p>
 * Description: 映射枚举
 * </p>
 * <p>
 * Copyright: Copyright (c) 2020
 * </p>
 * <p>
 * Company/Department: DataPipeline
 * </p>
 *
 * @author wanglijun
 * @version 1.0
 * @since 2020-07-18 22:22
 */
public class EnumUtils {


    private static Map<Class, EnumConfig> cache = new HashMap<Class, EnumConfig>();

    /**
     * 获得枚举，根据EnumMapping 注解，如果没有，则使用枚举名称
     *
     * @param c     枚举类
     * @param value 参考值
     * @return
     */
    public static Enum getEnumByValue(Class c, Object value) {
        if (!c.isEnum()) {
            throw new IllegalArgumentException(c.getName());
        }

        EnumConfig config = cache.get(c);
        if (config == null) {
            init(c);
            config = cache.get(c);
        }
        //测试 SQLServer 数据库的 tinyint 类型 会被转为 Short 而如果封装时Key的类型为Integer 则无法取出
        if (Short.class == value.getClass()) {
            value = ((Short) value).intValue();
        }
        return config.map.get(value);
    }

    public static Object getValueByEnum(Object en) {
        if (en == null) {
            return null;
        }
        Class c = en.getClass();

        EnumConfig config = cache.get(c);
        if (config == null) {

            init(c);
            config = cache.get(c);
        }

        return config.dbMap.get(en);

    }

    private static void initNoAnnotation(Class c) {
        Map<Object, Enum> map = new HashMap<Object, Enum>();
        Map<Enum, Object> map2 = new HashMap(); // db
        Enum[] temporaryConstants = getEnumValues(c);
        for (Enum e : temporaryConstants) {
            //用enum的名称
            Object key = e.name();
            map.put(key, e);
            map2.put(e, key);
        }
        EnumConfig config = new EnumConfig(map, map2);
        cache.put(c, config);
    }

    private static void init(Class c) {
        try {
            EnumMapping db = (EnumMapping) c.getAnnotation(EnumMapping.class);
            if (db == null) {
                //todo, use default
                initNoAnnotation(c);
            } else {
                String name = db.value();
                //参考Hibernate 对简单 Enum 进行支持
                String getter = EnumMapping.EnumType.STRING.equals(name) ? "toString" : EnumMapping.EnumType.ORDINAL.equals(name) ? "ordinal" : "get" + StringUtils.capitalize(name);
                Method m = c.getMethod(getter, new Class[]{});
                Map<Object, Enum> map = new HashMap<Object, Enum>();
                Map<Enum, Object> map2 = new HashMap(); // db
                Enum[] temporaryConstants = getEnumValues(c);
                for (Enum e : temporaryConstants) {

                    Object key = m.invoke(e, new Object[]{});
                    map.put(key, e);
                    map2.put(e, key);
                }
                EnumConfig config = new EnumConfig(map, map2);
                cache.put(c, config);
            }
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    private static Enum[] getEnumValues(Class c) {
        try {
            final Method values = c.getMethod("values");
            java.security.AccessController.doPrivileged(new java.security.PrivilegedAction<Void>() {
                @Override
                public Void run() {
                    values.setAccessible(true);
                    return null;
                }
            });
            Enum[] temporaryConstants;
            temporaryConstants = (Enum[]) values.invoke(null);
            return temporaryConstants;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    public static void main(String[] args) {
        Color c = Color.RED;
        Object value = EnumUtils.getValueByEnum(c);
        System.out.println(value);

        String a = "BLUE";
        Color e = (Color) EnumUtils.getEnumByValue(Color.class, 1);
        System.out.println(e);

    }

    @EnumMapping("value")
    public enum Color {
        //Red
        RED("RED", 1),
        //Blue
        BLUE("BLUE", 2);
        private String name;
        private int value;

        private Color(String name, int value) {
            this.name = name;
            this.value = value;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getValue() {
            return value;
        }

        public void setValue(int value) {
            this.value = value;
        }

    }

    public static class EnumConfig {
        Map<Object, Enum> map = new HashMap<>();
        Map<Enum, Object> dbMap = new HashMap(); // db

        public EnumConfig(Map<Object, Enum> map, Map<Enum, Object> dbMap) {
            this.map = map;
            this.dbMap = dbMap;
        }

    }
}
